Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add --screenshot to the typhos CLI #566

Merged
merged 7 commits into from
Aug 14, 2023
Merged

Conversation

klauer
Copy link
Contributor

@klauer klauer commented Aug 9, 2023

Description

Add typhos --screenshot filename_pattern to take screenshots of typhos displays prior to exiting early (in combination with --exit-after).

Motivation and Context

Larger changes to layouts coming in #563
We should check screenshots from the current master and compare them with #563 to ensure that the latter is strictly an improvement

How Has This Been Tested?

Test suite and interactively

Where Has This Been Documented?

This PR text and pre-release notes

Screenshots

$ typhos --fake-device ophyd.EpicsMotor[] --screenshot "{device.name}.png" --exit-after 1
[2023-08-09 09:40:41] - WARNING - Exiting typhos early due to --exit-after=1.0 CLI argument.
[2023-08-09 09:40:41] - INFO - Saving screenshot of 'Typhos Suite - EpicsMotor': 'EpicsMotor' to 'EpicsMotor.png'

EpicsMotor

@klauer klauer marked this pull request as ready for review August 9, 2023 16:46
@klauer klauer requested review from ZLLentz and tangkong August 9, 2023 16:52
)
continue

filename = filename_format.format(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that the CLI --screenshot can be formatted with the following variables:
suite_title, widget_title, device, name (or any attribute thereof)

This should be better communicated in the CLI description

Copy link
Contributor

@tangkong tangkong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the results here speak for themselves. A stray thought from me and not much else

handle = io.StringIO()
save_suite(suite, handle)
handle.seek(0)
devices = [device.name for device in suite.devices]
assert str(devices) in handle.read()


def test_suite_save(suite, monkeypatch):
def test_suite_save_screenshot(suite: TyphosSuite, device: MockDevice):
with tempfile.NamedTemporaryFile(mode="wb") as fp:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been using pytest's tmp_path fixtures for temporary file generation, and I'm surprised/ashamed I didn't know about python's built-in tempfile until just now. I wasn't able to google an appreciable difference between the two so I have no further feedback 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest fixtures generally feel like the right way of doing things in the test suite - I'm just more familiar with this standard lib one. I'll have to peek at the tmp_path API a bit out of curiosity...

image = display.to_image()
else:
# This is a fallback for if/when we don't have a TyphosDisplay
image = utils.take_widget_screenshot(display)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know how often this happens? I was under the impression most everything was a TyphosDeviceDisplay

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't ever happen in the current codebase. The truth is the following:

  1. I added utils.take_widget_screenshot first, added tests and had it all working
  2. Then I shifted to "it'd be better to save each device display"
  3. Then I recalled that I had already made to_image on TyphosDeviceDisplay (a few years ago perhaps?)
  4. Then I needed to justify the work from (1) so this else clause stayed in

Copy link
Member

@ZLLentz ZLLentz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me

I think the only thing missing is something you pointed out yourself: docs about the format strings that are accepted in the screenshot filename.

@@ -25,6 +29,31 @@

logger = logging.getLogger(__name__)


class TyphosArguments(types.SimpleNamespace):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of using placeholder classes like this for arg type handling


return launch_suite(suite, initial_size=initial_size)
return launch_suite(suite, initial_size=initial_size)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the cleanup/dedentation you did here

typhos/cli.py Outdated


def typhos_cli(args):
"""Command Line Application for Typhos."""
args = parser.parse_args(args)
args = typing.cast(TyphosArguments, parser.parse_args(args))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a built-in feature of argparse to fill a namespace instead of creating one. I'm not sure which way is better from a maintainability and type-checking standpoint.

args = parser.parse_args(args, TyphosArguments())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for that tip! I think your way is better than the type cast 👍

@@ -1405,6 +1405,9 @@ def add_device(self, device, macros=None):
self.macros = self._build_macros_from_device(device, macros=macros)
self.load_best_template()

if not self.windowTitle():
self.setWindowTitle(getattr(device, "name", ""))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity: which edge case is this fixing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All widgets in Qt can have window titles, as you already know, in case they're displayed as-is in the window manager.

TyphosDeviceDisplay or its subclasses may choose to customize the window title but they don't currently. This says "if there's no title set, at least give it the device name"

Then to answer your actual question: this title is then used during the screenshot process, with output saying "Taking a screenshot of (suite title) (device display title)":
[2023-08-09 09:40:41] - INFO - Saving screenshot of 'Typhos Suite - EpicsMotor': 'EpicsMotor' to 'EpicsMotor.png'

@@ -1704,3 +1704,62 @@ def set_no_edit_style(object: QtWidgets.QLineEdit):
"QLineEdit { background: transparent }"
)
object.setReadOnly(True)


def take_widget_screenshot(widget: QtWidgets.QWidget) -> Optional[QtGui.QImage]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how these utils aren't actually typhos-specific and may have broader use in the future

@klauer
Copy link
Contributor Author

klauer commented Aug 14, 2023

One CI job timed out during the test suite - re-running it prior to merging

@klauer klauer merged commit be1a7b7 into pcdshub:master Aug 14, 2023
7 of 9 checks passed
@klauer klauer deleted the enh_screenshot branch August 14, 2023 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants